package com.cong.security.core.code;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.cong.security.core.code.image.ImageCode;
import com.cong.security.core.properties.SecurityProperties;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.filter.OncePerRequestFilter;
import lombok.extern.slf4j.Slf4j;

/**
 * 继承OncePerRequestFilter，保证该过滤器只会被调用一次
 * 
 * @author single-聪
 *
 */
@Slf4j
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {

	/* 失败处理器,成功直接进入下一过滤器,所以不需要成功处理器 */
	private AuthenticationFailureHandler myAuthenticationFailureHandler;

	/* 存放需要拦截的Url */
	private Set<String> urls = new HashSet<>();

	private SecurityProperties securityProperties;

	public void setMyAuthenticationFailureHandler(AuthenticationFailureHandler myAuthenticationFailureHandler) {
		this.myAuthenticationFailureHandler = myAuthenticationFailureHandler;
	}

	public void setSecurityProperties(SecurityProperties securityProperties) {
		this.securityProperties = securityProperties;
	}

	private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

	private AntPathMatcher antPathMatcher = new AntPathMatcher();

	@Override
	public void afterPropertiesSet() throws ServletException {
		super.afterPropertiesSet();
		log.info("配置图形验证码拦截路径[{}] ", securityProperties.getCode().getImage().getUrl());
		// 放入配置文件中配置
		String[] configUrls = StringUtils
				.splitByWholeSeparatorPreserveAllTokens(securityProperties.getCode().getImage().getUrl(), ",");
		for (String string : configUrls) {
			urls.add(string);
		}
	}

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		// 登录请求才起作用且必须是post请求
		log.info("图形验证码请求路径[{}] ", request.getRequestURI());
		// 判断用户登录请求是否需要先进行验证码校验
		boolean match = false;
		// 循环判断当前请求是否需要进行图形验证码校验
		for (String url : urls) {
			if (antPathMatcher.match(url, request.getRequestURI())) {
				match = true;
			}
		}
		// 如果需要进行验证码校验
		if (match) {
			try {
				log.info("开始校验图形验证码");
				validate(new ServletWebRequest(request));
			} catch (CodeException e) {
				// 图形验证码校验出现问题,走失败处理器(验证码成功是敲门砖,验证码失败不会走用户名密码登录,一方面降低数据库请求次数,一方面防止别人拿到登陆请求恶意攻击,造成数据库压力过大)
				myAuthenticationFailureHandler.onAuthenticationFailure(request, response, e);
				return;
			}
		}
		filterChain.doFilter(request, response);
	}

	private void validate(ServletWebRequest request) throws ServletRequestBindingException {
		// 获取图形验证码信息,APP中的验证码值会从缓存中获取,完全抛弃session
		ImageCode imageCode = (ImageCode) sessionStrategy.getAttribute(request, CodeController.SESSION_KEY);
		// 
		String code = ServletRequestUtils.getStringParameter(request.getRequest(), "imageCode");
		log.info("imageCode值为[{}] ", imageCode);
		// 用户未输入值
		if (StringUtils.isBlank(code)) {
			throw new CodeException("图形验证码的值不能为空");
		}
		// session中不存在数据
		if (imageCode == null) {
			throw new CodeException("图形验证码不存在");
		}
		// 验证码超时过期,缓存中不存在也是过期
		if (imageCode.isExpried()) {
			sessionStrategy.removeAttribute(request, CodeController.SESSION_KEY);
			throw new CodeException("图形验证码已过期");
		}
		// 验证码是否匹配
		if (!StringUtils.equalsIgnoreCase(imageCode.getCode(), code)) {
			throw new CodeException("图形验证码不匹配");
		}
		// 验证成功，将验证码从session中移除
		sessionStrategy.removeAttribute(request, CodeController.SESSION_KEY);
	}
}
